#include "Client.h"

#include <Poco/ScopedLock.h>
#include <Poco/Net/NetworkInterface.h>
#include "client/ui/cuiBase.h"
#include "client/misc/cConfig.h"
#include "client/net/cDiscovery.h"
#include "client/net/cAnnounce.h"
#include "client/net/cRingRequest.h"
#include "client/net/cRingReply.h"
#include "client/net/cRingDeny.h"
#include "client/net/cRingDisconnect.h"
#include "misc/Interface.h"
#include "misc/Timer.h"
/*********************************************************/
void Client::run()
{
	Event* process = Event::Instance(PROCESS_EVENT);
	Event* quit = Event::Instance(QUIT_EVENT);
	Interface& ui = Interface::Instance();

	mutex.lock();
	while ( !quitFlag ) {
		mutex.unlock();
		process->wait();		// Wait for network traffic or user input

		// Process ui commands
		std::string uiCmd;
		if ( ui.peek(uiCmd) ) {
			ui.pop();
			cuiBase* cmd = cuiBase::parse(uiCmd);
			if ( cmd ) {
				try {
					tPool.start(*cmd);
				} catch( Poco::NoThreadAvailableException& e ) {
					tPool.addCapacity(5);
					tPool.start(*cmd);
				}
			}
		}

		// Process network packets
		while ( net->available() ) {
			Poco::Net::SocketAddress	addr;
			std::string msg = net->pop(addr);
			try {
				if ( getLocalAddress().compare(addr.toString()) == 0 ) {
					continue;
				}

				cMsg* p = cMsg::deserialize(msg);
				if ( p ) {
					p->setSenderAddr(addr);
					try {
						tPool.start(*p);
					} catch( Poco::NoThreadAvailableException& e ) {
						tPool.addCapacity(5);		// Increase capacity
						tPool.start(*p);
					}
				}
			} catch(...) {
				ui << "Received unicast junk: " << msg << std::endl;
				ui.flush();
			}
		}

		mutex.lock();
	}
	mutex.unlock();

	quit->set();
}
/*********************************************************/
unsigned long Client::getID()
{
	mutex.lock();
	unsigned long a = counter;
	counter++;
	mutex.unlock();
	return a;
}
/*********************************************************/
void Client::wait()
{
	Event* quit = Event::Instance(QUIT_EVENT);
	quit->wait();
}
/*********************************************************/
bool Client::waitForAck(const std::string& host, const unsigned long& msgID)
{
	mutex.lock();
	bool acked = false;
	int time = 0;
	while ( !acked && (time < 5000)) {
		ACKMAP::iterator it = ackmap.find(host);
		if ( it != ackmap.end() ) {
			std::map<unsigned long, std::string>::iterator it2 = it->second.find(msgID);
			if ( it2 == it->second.end() ) {
				acked = true;
			}
		} else {
			acked = true;
		}
		mutex.unlock();
		usleep(10000);
		time += 10;
		mutex.lock();
	}
	mutex.unlock();	
	return acked;
}
/*********************************************************/
void Client::start(const std::string& name)
{
	Poco::ScopedLock<Poco::Mutex>	sLock(mutex);
	Event* process = Event::Instance(PROCESS_EVENT);
	Event* quit = Event::Instance(QUIT_EVENT);
	cConfig& cfg = cConfig::Instance();
	quitFlag = false;
	quit->reset();
	process->reset();
	ringName = name;

	// Set the ringname in the configuration instance
	cfg.setRingName(ringName);

	// Validate the network interface
	Poco::Net::NetworkInterface iface = Poco::Net::NetworkInterface::forName(cfg.getNetworkInterface());
	unsigned int av_port = UDPNet::getAvailablePort(iface.address().toString(), _MIN_PORT_RANGE_, _MAX_PORT_RANGE_);
	net = new( UDPNet )(iface.address().toString(), av_port);
	net->start(PROCESS_EVENT);

	// Start the interface
	Interface& ui = Interface::Instance();
	ui.start(PROCESS_EVENT);

	int port;
	std::string addr;
	net->getLocalAddress(addr, port);
	ui << "Client succesfully started on ip address: " << addr << ":" << port << std::endl;

	// Start the multicast class
	multicast = new( MultiNet )(cfg.getNetworkInterface());
	multicast->start(PROCESS_EVENT);
	multicast->joinGroup(cfg.getMulticastGroup().host().toString(), cfg.getMulticastGroup().port());	// Join a multicasting group
	multicast->setLoopback(true);

	// Wait until we get a broadcast from the nameserver
	ui << "Obtaining NameServer address..." << std::endl;
	ui.flush();

	// Provoke the nameserver to reply using a discovery message
	cDiscovery	disc;
	broadcast(&disc);

	Event* 			check = Event::Instance("CHECK_EVENT");
	Timer  			clock("CHECK_EVENT", 5000, true);
	Poco::Timestamp		time;
	bool 			found = false;

	while ( !found ) {
		check->wait();

		if ( time.elapsed() > (cfg.getNameServerDelay()*1000) ) {
			ui << "Couldn't obtain NameServer address!" << std::endl;
			ui.flush();
			sleep(5);
			multicast->stop();
			net->stop();
			delete( multicast );
			delete( net );
			multicast = 0;
			net = 0;
			quit->set();
			clock.stop();
			ui.stop();
			return;
		}

		while ( multicast->available() ) {
			std::string msg = multicast->peek();
			multicast->pop();

			cMsg* packet = cMsg::deserialize(msg);
			if ( packet ) {
				cAnnounce* ann = dynamic_cast<cAnnounce*>(packet);
				if ( ann ) {
					ann->execute();
					found = true;
					ui << "NameServer found @ " << nsAddr.host().toString() << ":" << nsAddr.port() << std::endl;
					ui.flush();
					break;
				}
			}
		}
	}

	// Build a RingRequest packet and send it to the nameserver
	ui << "Requesting ring connection: " << cfg.getRingName() << "..." << std::endl;
	ui.flush();

	cRingRequest	rReq;
	rReq.setRingName(cfg.getRingName());
	net->sendTo(rReq.serialize(), nsAddr.toString());

	// Wait for the response from the nameserver
	check->reset();
	found = false;
	time = Poco::Timestamp();
	while ( !found ) {
		check->wait();

		while ( net->available() ) {
			Poco::Net::SocketAddress	senderAddr;
			std::string msg = net->pop(senderAddr);

			cMsg* packet = cMsg::deserialize(msg);
			if ( packet ) {
				packet->setSenderAddr(senderAddr);
				cRingReply* rep = dynamic_cast<cRingReply*>(packet);
				if ( rep ) {
					rep->execute();
					found = true;
				} else {
					cRingDeny* den = dynamic_cast<cRingDeny*>(packet);
					if ( den ) {
						if ( den->getName() == ringName ) {
							den->execute();
							sleep(5);
							multicast->stop();
							net->stop();
							delete(multicast);
							delete(net);
							quit->set();
							clock.stop();
							ui.stop();
							multicast = 0;
							net = 0;
							return;
						}
					}
				}
				delete( packet );
			}
		}

		if ( time.elapsed() > (cfg.getNameServerDelay()*1000) ) {
			ui << "Couldn't obtain server address from nameserver!" << std::endl;
			ui.flush();
			sleep(5);
			multicast->stop();
			net->stop();
			delete( multicast );
			delete( net );
			quit->set();
			clock.stop();
			ui.stop();
			multicast = 0;
			net = 0;
			return;
		}
	}
	clock.stop();

	// Set the server address 
	net->setResponseAddress(ringAddr.host().toString(), ringAddr.port());
	ui << "Received server address from nameserver: " << ringAddr.toString() << std::endl;
	ui.flush();

	// Start the local thread
	lThread.start((*this));

	// Give the thread some time
	usleep(200);

	ui << "Client connected to ring: " << ringName << "@" << ringAddr.toString() << std::endl;
	ui.flush();
}
/*********************************************************/
void Client::stop()
{
	// Let the nameserver know we are disconnecting from the ring node
	cRingDisconnect	rDisc;
	rDisc.setName(ringName);
	net->sendTo(rDisc.serialize(), nsAddr.toString());
	
	// Start the stop sequence
	Event* process = Event::Instance(PROCESS_EVENT);
	mutex.lock();
	process->set();
	quitFlag = true;
	mutex.unlock();

	if ( lThread.isRunning() ) {
		lThread.join();
	}
	mutex.lock();

	// Stop the interface
	Interface& ui = Interface::Instance();
	ui.stop();

	// Stop the network ( UDPNet )
	if ( net ) {
		net->stop();
		delete( net );
	}

	// Stop the multicast class
	if ( multicast ) {
		multicast->stop();
		delete( multicast );
	}
	mutex.unlock();
}
/*********************************************************/
std::string Client::getLocalAddress() const
{
	mutex.lock();
	int port;
	std::string host;
	net->getLocalAddress(host, port);
	std::ostringstream ostr;
	ostr << host << ":" << port;
	mutex.unlock();
	return ostr.str();
}
/*********************************************************/
bool Client::send(const cMsg* msg)
{
	Interface& iface = Interface::Instance();

	mutex.lock();
	std::string host;
	int port;
	net->getResponseAddress(host, port);
	std::ostringstream	ostr;
	ostr << host << ":" << port;
	std::string m = msg->serialize();
	unsigned long id = msg->getID();
	ackmap[ostr.str()][id] = m;
	net->send(m);
	mutex.unlock();

	// Wait for the ack
	int attempts = 0;
	while (!waitForAck(ostr.str(), id) && (attempts < 5)) {
		attempts++;
		mutex.lock();
		net->sendTo(m, ostr.str());
		mutex.unlock();
		iface << "Failed to send message, no ack returned... retrying" << std::endl;
		iface << m << std::endl;
		iface.flush();
	}

	if ( attempts == 5 ) {
		return false;
	} else {
		return true;
	}
}
/*********************************************************/
bool Client::sendTo(const cMsg* msg, const std::string& target) 
{
	Interface& iface = Interface::Instance();

	mutex.lock();
	std::string m = msg->serialize();
	ackmap[target][msg->getID()] = m;
	net->sendTo(m, target);
	mutex.unlock();

	// Wait for the ack
	int attempts = 0;
	while (!waitForAck(target, msg->getID()) && (attempts < 5)) {
		attempts++;
		mutex.lock();
		net->sendTo(m, target);
		mutex.unlock();
		iface << "Failed to send message, no ack returned... retrying" << std::endl;
		iface << m << std::endl;
		iface.flush();
	}

	if ( attempts == 5 ) {
		return false;
	} else {
		return true;
	}
}
/*********************************************************/
void Client::broadcast(const cMsg* m)
{
	mutex.lock();
	multicast->send(m->serialize());
	mutex.unlock();
}
/*********************************************************/
Client& Client::Instance()
{
	static Client pInstance;
	return pInstance;
}
/*********************************************************/
